using System;
using NSubstitute.Routing;

// Disable nullability for entry-point API
#nullable disable annotations

namespace NSubstitute.Core
{
    public class WhenCalled<T>
    {
        private readonly T _substitute;
        private readonly Action<T> _call;
        private readonly MatchArgs _matchArgs;
        private readonly ICallRouter _callRouter;
        private readonly IThreadLocalContext _threadContext;
        private readonly IRouteFactory _routeFactory;

        public WhenCalled(ISubstitutionContext context, T substitute, Action<T> call, MatchArgs matchArgs)
        {
            _substitute = substitute;
            _call = call;
            _matchArgs = matchArgs;
            _callRouter = context.GetCallRouterFor(substitute!);
            _routeFactory = context.RouteFactory;
            _threadContext = context.ThreadContext;
        }

        /// <summary>
        /// Perform this action when called.
        /// </summary>
        /// <param name="callbackWithArguments"></param>
        public void Do(Action<CallInfo> callbackWithArguments)
        {
            _threadContext.SetNextRoute(_callRouter, x => _routeFactory.DoWhenCalled(x, callbackWithArguments, _matchArgs));
            _call(_substitute);
        }

        /// <summary>
        /// Perform this configured callback when called.
        /// </summary>
        /// <param name="callback"></param>
        public void Do(Callback callback)
        {
            _threadContext.SetNextRoute(_callRouter, x => _routeFactory.DoWhenCalled(x, callback.Call, _matchArgs));
            _call(_substitute);
        }

        /// <summary>
        /// Do not call the base implementation on future calls. For use with partial substitutes.
        /// </summary>
        public void DoNotCallBase()
        {
            _threadContext.SetNextRoute(_callRouter, x => _routeFactory.DoNotCallBase(x, _matchArgs));
            _call(_substitute);
        }

        /// <summary>
        /// Call the base implementation of future calls. For use with non-partial class substitutes.
        /// </summary>
        public void CallBase()
        {
            _threadContext.SetNextRoute(_callRouter, x => _routeFactory.CallBase(x, _matchArgs));
            _call(_substitute);
        }

        /// <summary>
        /// Throw the specified exception when called.
        /// </summary>
        public void Throw(Exception exception) =>
            Do(ci => throw exception);

        /// <summary>
        /// Throw an exception of the given type when called.
        /// </summary>
        public TException Throw<TException>() where TException : Exception, new()
        {
            var exception = new TException();
            Do(_ => throw exception!);
            return exception;
        }

        /// <summary>
        /// Throw an exception generated by the specified function when called.
        /// </summary>
        public void Throw(Func<CallInfo, Exception> createException) =>
            Do(ci => throw createException(ci));
    }
}